#include <Kharlia/Core/Common.h>
#include <Kharlia/Core/ObjectBase.h>
#include <Kharlia/Core/ObjectUtil.h>
#include <boost/python/raw_function.hpp>

namespace kharlia { namespace core {

CObject::CObject(PyObject* self) {
    if (self == Py_None || self == NULL)
        throw RuntimeError("invalid reference to self");
    if (!PyObject_IsInstance(self, reinterpret_cast<PyObject*>(objects::class_type().get())))
        throw RuntimeError("self is not an instance of Boost.Python.instance");
    mSelf = self;
}

CObject::~CObject() {
    mSelf = NULL;
}

//void CObject::_DelObject() {
//    int x = 5;
//}

object CObject::GetSelfAttr(object name) const {
    return object(handle<>(PyObject_GetAttr(mSelf, name.ptr())));
}

object CObject::GetSelfAttr(const char* name) const {
    return object(handle<>(PyObject_GetAttrString(mSelf, name)));
}

void CObject::SetSelfAttr(object name, object value) const {
    int r = PyObject_SetAttr(mSelf, name.ptr(), value.ptr());
    if (r == -1)
        throw_error_already_set();
}

void CObject::SetSelfAttr(const char* name, object value) const {
    int r = PyObject_SetAttrString(mSelf, name, value.ptr());
    if (r == -1)
        throw_error_already_set();
}

object CObject::GetSuper(object type) const {
    handle<> ret(PyObject_CallFunctionObjArgs((PyObject*)&PySuper_Type, type.ptr(), mSelf, NULL));
    return object(ret);
}

object CObject::GetSuper(py::type_info type) const {
    return GetSuper(GetTypeObject(type));
}

KH_NO_GC(CObject)

// This function regulates the initialization of the underlying Boost.Python.instance.
//
// This works by ensuring only the topmost C++ constructor is called once even with
// multiple calls to this function by classes in the MRO.
//
// The argument 'cls' should be the Boost.Python.class to be initialized. If it
// is None, then this will be located using self.NativeBase which should provide
// the topmost Boost.Python.class in the MRO.
static object InitNative(tuple args, dict kwds) {
    // cls, self, *args, **kwds
    using namespace objects;

    object selfo, cls, initfunc, argsslice;
    instance<>* self = NULL;
    bool inited = false;

    selfo = args[1];
    if (!PyObject_IsInstance(selfo.ptr(), (PyObject*)class_type().get())) {
        PyErr_SetString(PyExc_TypeError, "self must be instance of Boost.Python.instance");
        throw_error_already_set();
    }
    self = downcast<instance<>>(selfo.ptr());
    // instance<>::objects will be null if the instance holder has not been
    // constructed
    if (self->objects) {
        // If its not holding a CObject something is wrong somewhere
        assert(self->objects->holds(type_id<CObject>(), false));
    }
    else {
        cls = args[0];
        if (cls.is_none())
            // Retrieve the topmost native base class
            cls = getattr(selfo, "NativeBase", object());
        if (!PyObject_IsInstance(cls.ptr(), (PyObject*)class_metatype().get())) {
            PyErr_SetString(PyExc_TypeError, "cls must be instance of Boost.Python.class");
            throw_error_already_set();
        }
        initfunc = cls.attr("__init__");
        // Exclude cls from arguments sent to constructor
        argsslice = args.slice(1, nil);
        // Call the constructor
        handle<>(PyObject_Call(initfunc.ptr(), argsslice.ptr(), kwds.ptr()));
    }
    return object();
}

//static void _DelObjectImpl(objects::instance<>* self) {
//    assert(self->objects);
//    CObject* realself = (CObject*)self->objects->holds(type_id<CObject>(), false);
//    assert(realself);
//    realself->_DelObject();
//}


namespace util {

FValueToPtrRegistry GValueToSharedPtrRegistry;
object GConstructObjectFunc;
object GResolveNameFunc;

object GetWeakRef(object obj, object cb) {
    if (PyWeakref_CheckRef(obj.ptr()))
        return obj;
    handle<> wr(PyWeakref_NewRef(obj.ptr(), cb.is_none()? NULL: cb.ptr()));
    return object(wr);
}

bool Iterate(const object& nextFunc, object& out) {
    try {
        out = nextFunc();
        return true;
    }
    catch (error_already_set&) {
        if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
            PyErr_Clear();
            return false;
        }
        throw;
    }
}

} // namespace util

void _InitObjectBindings() {
    object basemod = import("kharlia.base");
    GConstructObjectFunc = basemod.attr("ConstructObject");
    GResolveNameFunc = basemod.attr("ResolveName");

    class_<CObject, shared_ptr<CObject>, noncopyable>
        ("CObjectBase", init<>())
        //.def("__del__", &CObject::_DelObject)
        .def("InitNative", raw_function(&InitNative, 2))
        .staticmethod("InitNative")
        ;

    KH_IMPL_GC(CObject);

    // Insert new __del__
    //{
    //    object typeO = GetTypeObject(type_id<CObject>());
    //    PyTypeObject* type = (PyTypeObject*)typeO.ptr();
    //    type->tp_del = (destructor)&_DelObjectImpl;
    //    
    //    PyObject* ndelstr = NULL;
    //    PyWrapperDescrObject* bdelwrap = NULL, *ndelwrap = NULL;

    //    ndelstr = PyString_FromString("__del__");
    //    if (!ndelstr)
    //        goto cleanup;

    //    bdelwrap = (PyWrapperDescrObject*)_PyType_Lookup(type->tp_base, ndelstr);
    //    if (!bdelwrap) {
    //        PyErr_SetString(PyExc_RuntimeError, "base __del__ wrapper not found");
    //        goto cleanup;
    //    }
    //    if (Py_TYPE(bdelwrap) != &PyWrapperDescr_Type) {
    //        goto cleanup;
    //    }
    //    bdelwrap->d_wrapped = &_DelObjectImpl;

    //    cleanup:

    //    Py_XDECREF(ndelstr);
    //    Py_XDECREF(ndelwrap);
    //    if (PyErr_Occurred())
    //        throw_error_already_set();
    //}

}

}} // namespace kharlia::core